package wiki.kaizen.cloud.plugin.spring.scan.compent;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;


@Component
@Slf4j
public class SpringBeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        if (SpringBeanUtil.applicationContext == null) {
            SpringBeanUtil.applicationContext = applicationContext;
        }
    }

    // 获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    // 通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    // 通过class获取Bean.
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        try{
            return applicationContext.getBean(clazz);
        }catch (Exception e1){
            try {
                char[] cs = clazz.getSimpleName().toCharArray();
                cs[0] += 32;// 首字母大写到小写
                return (T) getApplicationContext().getBean(String.valueOf(cs));
            } catch (Exception e2) {
                Class<?>[] interfaces = clazz.getInterfaces();
                for (Class<?> interfaceClazz : interfaces) {
                    if (clazz.getSimpleName().contains(interfaceClazz.getSimpleName())) {
                        char[] cs = interfaceClazz.getSimpleName().toCharArray();
                        cs[0] += 32;// 首字母大写到小写
                        return (T) getApplicationContext().getBean(String.valueOf(cs));
                    }
                }
                log.error("未能从Spring容器中获取到 Bean: {}",clazz.getName());
                return null;
            }
        }
    }

    // 通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    public static <A extends Annotation> Map<String, Object> getBeansWithAnnotation(Class<A> annoClass){
        return applicationContext.getBeansWithAnnotation(annoClass);
    }

    /**
     * 根据注解及注解的值获取 对应的Bean
     * 要求 注解必须包含方法value 且为空参
     * */
    @SuppressWarnings("unchecked")
    public static < B,A extends Annotation, E> B getBean(Class<A> annoClass, E em){
        Objects.requireNonNull(annoClass,"注解的值不能为空");
        Objects.requireNonNull(em,"注解value对应的值不能为空");
        Map<String, Object> beans = getBeansWithAnnotation(annoClass);
        Method value;
        try {
            value = annoClass.getDeclaredMethod("value");
        } catch (NoSuchMethodException e) {
            log.error(annoClass.getSimpleName()+"注解,未设置value方法");
            return null;
        }
        value.setAccessible(true);
        Optional<B> any = beans.values().stream().filter(
            bean -> {
                A anno = Optional.ofNullable(
                    bean.getClass().getAnnotation(annoClass)
                ).orElse(
                    bean.getClass().getSuperclass().getAnnotation(annoClass)
                );
                if (anno == null){
                    log.error("未正确获取注解,请给注解设置@Inherited");
                    return false;
                }
                Object invoke;
                try {
                    invoke =  value.invoke(anno);
                } catch (IllegalAccessException | InvocationTargetException ex) {
                    return false;
                }
                try{
                    if (invoke.getClass().isArray()){
                        E[] es = (E[]) invoke;
                        return ArrayUtils.contains(es, em);
                    }else{
                        E e = (E)invoke ;
                        return em == e || em.equals(e);
                    }
                }catch (Exception ignore){
                    log.error("value 类型错误无法转换");
                    return false;
                }
            }
        ).map(bean -> (B) bean).findAny();
        return any.orElseGet(
            ()->{
                log.error("未能从Spring容器中获取到{}对应的Bean",em.getClass());
                return null;
            }
        );
    }
}
